{
 "cells": [
  {
   "cell_type": "raw",
   "metadata": {
    "raw_mimetype": "text/restructuredtext"
   },
   "source": [
    ".. _nb_custom:"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Problem Definition"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "In the following different ways of loading or implementing an optimization problem in our framework are discussed."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### By Class\n",
    "\n",
    "A very detailed description of defining a problem through a class is already provided in the [Getting Started Guide](../getting_started.ipynb).\n",
    "The following definition of a simple optimization problem with **one** objective and **two** constraints is considered. The problem has two constants, *const_1* and *const_2*, which can be modified by initiating the problem with different parameters. By default, it consists of 10 variables, and the lower and upper bounds are within $[-5, 5]$ for all variables. \n",
    "\n",
    "**Note**: The example below uses the `autograd` library, which calculates the gradients through automatic differentiation."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "import autograd.numpy as anp\n",
    "\n",
    "from pymoo.model.problem import Problem\n",
    "\n",
    "class MyProblem(Problem):\n",
    "\n",
    "    def __init__(self, const_1=5, const_2=0.1):\n",
    "\n",
    "        # define lower and upper bounds -  1d array with length equal to number of variable\n",
    "        xl = -5 * anp.ones(10)\n",
    "        xu = 5 * anp.ones(10)\n",
    "\n",
    "        super().__init__(n_var=10, n_obj=1, n_constr=2, xl=xl, xu=xu, evaluation_of=\"auto\")\n",
    "\n",
    "        # store custom variables needed for evaluation\n",
    "        self.const_1 = const_1\n",
    "        self.const_2 = const_2\n",
    "\n",
    "    def _evaluate(self, x, out, *args, **kwargs):\n",
    "        f = anp.sum(anp.power(x, 2) - self.const_1 * anp.cos(2 * anp.pi * x), axis=1)\n",
    "        g1 = (x[:, 0] + x[:, 1]) - self.const_2\n",
    "        g2 = self.const_2 - (x[:, 2] + x[:, 3])\n",
    "\n",
    "        out[\"F\"] = f\n",
    "        out[\"G\"] = anp.column_stack([g1, g2])\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "After creating a problem object, the evaluation function can be called. The `return_values_of` parameter can be overwritten to modify the list of returned parameters. The gradients for the objectives `dF` and constraints `dG` can be obtained as follows:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "problem = MyProblem()\n",
    "F, G, CV, feasible, dF, dG = problem.evaluate(np.random.rand(100, 10),\n",
    "                                              return_values_of=[\"F\", \"G\", \"CV\", \"feasible\", \"dF\", \"dG\"])\n"
   ]
  },
  {
   "cell_type": "raw",
   "metadata": {
    "raw_mimetype": "text/restructuredtext"
   },
   "source": [
    ".. _nb_problem_elementwise:"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Elementwise Evaluation**\n",
    "\n",
    "If the problem can not be executed using matrix operations, a serialized evaluation can be indicated using the `elementwise_evaluation=True` flag. If the flag is set, then an outer loop is already implemented, an `x` is only a **one**-dimensional array."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [],
   "source": [
    "class MyProblem(Problem):\n",
    "\n",
    "    def __init__(self, **kwargs):\n",
    "        super().__init__(n_var=2, n_obj=1, elementwise_evaluation=True, **kwargs)\n",
    "\n",
    "    def _evaluate(self, x, out, *args, **kwargs):\n",
    "        out[\"F\"] = x.sum()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### By Function\n",
    "\n",
    "Another way of defining a problem is through functions. One the one hand, many function calls need to be performed to evaluate a set of solutions, but on the other hand, it is a very intuitive way of defining a problem."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {
    "code": "usage_problem.py",
    "section": "from_function"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "F: [[22.7113447  65.57216667]\n",
      " [21.85725341 66.87914511]\n",
      " [20.02634235 68.74647074]]\n",
      "\n",
      "CV: [[3.42655019]\n",
      " [3.11272633]\n",
      " [2.20637444]]\n"
     ]
    }
   ],
   "source": [
    "import numpy as np\n",
    "from pymoo.model.problem import FunctionalProblem\n",
    "\n",
    "\n",
    "objs = [\n",
    "    lambda x: np.sum((x - 2) ** 2),\n",
    "    lambda x: np.sum((x + 2) ** 2)\n",
    "]\n",
    "\n",
    "constr_ieq = [\n",
    "    lambda x: np.sum((x - 1) ** 2)\n",
    "]\n",
    "\n",
    "\n",
    "problem = FunctionalProblem(10,\n",
    "                            objs,\n",
    "                            constr_ieq=constr_ieq,\n",
    "                            xl=np.array([-10, -5, -10]),\n",
    "                            xu=np.array([10, 5, 10])\n",
    "                            )\n",
    "\n",
    "F, CV = problem.evaluate(np.random.rand(3, 10))\n",
    "\n",
    "print(f\"F: {F}\\n\")\n",
    "print(f\"CV: {CV}\")\n",
    "\n",
    "# END from_string"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### By String\n",
    "\n",
    "In our framework, various test problems are already implemented and available by providing the corresponding problem name we have assigned to it. A couple of problems can be further parameterized by providing the number of variables, constraints, or other problem-dependent constants."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {
    "code": "usage_problem.py",
    "section": "from_string"
   },
   "outputs": [],
   "source": [
    "from pymoo.factory import get_problem\n",
    "\n",
    "p = get_problem(\"dtlz1_-1\", n_var=20, n_obj=5)\n",
    "\n",
    "# create a simple test problem from string\n",
    "p = get_problem(\"Ackley\")\n",
    "\n",
    "# the input name is not case sensitive\n",
    "p = get_problem(\"ackley\")\n",
    "\n",
    "# also input parameter can be provided directly\n",
    "p = get_problem(\"dtlz1_-1\", n_var=20, n_obj=5)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## API"
   ]
  },
  {
   "cell_type": "raw",
   "metadata": {
    "raw_mimetype": "text/restructuredtext"
   },
   "source": [
    ".. class:: pymoo.model.problem.Problem\n",
    "\n",
    "   .. automethod:: evaluate(X)\n",
    "   .. automethod:: pareto_front(X)\n"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.7.4"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
